#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include "util.h"

#define PIECES_LEVEL (TRACE + 1)

// DvmhPieces
DvmhPieces *piecesNew(int size, int rank) {
    DvmhPieces *pieces;
    pieces = (DvmhPieces *)malloc(sizeof(DvmhPieces));
    pieces->piecesCount = size;
    if (size > 0){
        pieces->pieces = (Interval **)malloc(sizeof(Interval *) * size);
        int i;
        for (i = 0; i < size; i++)
            if (rank > 0)
                pieces->pieces[i] = (Interval *)malloc(sizeof(Interval) * rank);
            else
                pieces->pieces[i] = 0;
    } else
        pieces->pieces = 0;
    return pieces;
}

DvmhPieces *piecesDup(int rank, DvmhPieces *p) {
    DvmhPieces *res = piecesNew(p->piecesCount, rank);
    if (rank > 0) {
        int i;
        for (i = 0; i < p->piecesCount; i++)
            memcpy(res->pieces[i], p->pieces[i], sizeof(Interval) * rank);
    }
    return res;
}

void piecesDelete(DvmhPieces *p) {
    if (p) {
        int i;
        for (i = 0; i < p->piecesCount; i++)
            if (p->pieces[i])
                free(p->pieces[i]);
        if (p->pieces)
            free(p->pieces);
        free(p);
    }
}

void piecesAppend(int rank, DvmhPieces *p1, DvmhPieces *p2) {
    if (p2->piecesCount > 0) {
        dvmh_log(PIECES_LEVEL, "piecesAppend");
        custom_log(PIECES_LEVEL, piecesOut, rank, p1);
        dvmh_log(PIECES_LEVEL, "and");
        custom_log(PIECES_LEVEL, piecesOut, rank, p2);
        if (p1->pieces)
            p1->pieces = (Interval **)realloc(p1->pieces, sizeof(Interval *) * (p1->piecesCount + p2->piecesCount));
        else
            p1->pieces = (Interval **)malloc(sizeof(Interval *) * (p1->piecesCount + p2->piecesCount));
        int i;
        for (i = 0; i < p2->piecesCount; i++) {
            if (rank > 0) {
                p1->pieces[p1->piecesCount + i] = (Interval *)malloc(sizeof(Interval) * rank);
                memcpy(p1->pieces[p1->piecesCount + i], p2->pieces[i], sizeof(Interval) * rank);
            } else
                p1->pieces[p1->piecesCount + i] = 0;
        }
        p1->piecesCount += p2->piecesCount;
        dvmh_log(PIECES_LEVEL, "=");
        custom_log(PIECES_LEVEL, piecesOut, rank, p1);
    }
}

void piecesAppendOne(int rank, DvmhPieces *p, Interval *inter) {
    if (p->pieces)
        p->pieces = (Interval **)realloc(p->pieces, sizeof(Interval *) * (p->piecesCount + 1));
    else
        p->pieces = (Interval **)malloc(sizeof(Interval *) * (p->piecesCount + 1));
    p->piecesCount++;
    if (rank > 0) {
        p->pieces[p->piecesCount - 1] = (Interval *)malloc(sizeof(Interval) * rank);
        memcpy(p->pieces[p->piecesCount - 1], inter, sizeof(Interval) * rank);
    } else
        p->pieces[p->piecesCount - 1] = 0;
}

DvmhPieces *piecesSubtractOneOne(int rank, Interval *inter1, Interval *inter2) {
    dvmh_log(PIECES_LEVEL, "piecesSubtractOneOne");
    custom_log(PIECES_LEVEL, piecesOutOne, rank, inter1);
    dvmh_log(PIECES_LEVEL, "and");
    custom_log(PIECES_LEVEL, piecesOutOne, rank, inter2);
    DvmhPieces *res = piecesNew(0, rank);
    int interCount = 0;
    int i;
    for (i = 0; i < rank; i++)
        interCount += inter1[i][0] <= inter2[i][1] && inter2[i][0] <= inter1[i][1];
    if (interCount < rank) {
        piecesAppendOne(rank, res, inter1);
    } else if (rank > 0) {
        Interval rest[rank];
        memcpy(rest, inter1, sizeof(Interval) * rank);
        int r;
        for (r = 0; r < rank; r++) {
            if (rest[r][0] < inter2[r][0]) {
                long hi = rest[r][1];
                if (hi >= inter2[r][0])
                    hi = inter2[r][0] - 1;
                if (hi - rest[r][0] >= 0) {
                    Interval toInsert[rank];
                    memcpy(toInsert, rest, sizeof(Interval) * rank);
                    toInsert[r][1] = hi;
                    piecesAppendOne(rank, res, toInsert);
                }
                rest[r][0] = hi + 1;
            }
            if (rest[r][1] > inter2[r][1]) {
                long lo = rest[r][0];
                if (lo <= inter2[r][1])
                    lo = inter2[r][1] + 1;
                if (rest[r][1] - lo >= 0) {
                    Interval toInsert[rank];
                    memcpy(toInsert, rest, sizeof(Interval) * rank);
                    toInsert[r][0] = lo;
                    piecesAppendOne(rank, res, toInsert);
                }
                rest[r][1] = lo - 1;
            }
            if (rest[r][1] - rest[r][0] < 0)
                break;
        }
    }
    dvmh_log(PIECES_LEVEL, "=");
    custom_log(PIECES_LEVEL, piecesOut, rank, res);
    return res;
}

void piecesSubtractOne(int rank, DvmhPieces *p, Interval *inter) {
    int i;
    for (i = 0; i < p->piecesCount; i++) {
        DvmhPieces *toInsert = piecesSubtractOneOne(rank, p->pieces[i], inter);
        if (toInsert->piecesCount == 0) {
            if (i < p->piecesCount - 1)
                memcpy(p->pieces[i], p->pieces[p->piecesCount - 1], sizeof(Interval) * rank);
            if (p->pieces[p->piecesCount - 1])
                free(p->pieces[p->piecesCount - 1]);
            p->piecesCount--;
            i--;
        } else {
            if (rank > 0)
                memcpy(p->pieces[i], toInsert->pieces[toInsert->piecesCount - 1], sizeof(Interval) * rank);
            if (toInsert->pieces[toInsert->piecesCount - 1])
                free(toInsert->pieces[toInsert->piecesCount - 1]);
            toInsert->piecesCount--;
            piecesAppend(rank, p, toInsert);
            piecesDelete(toInsert);
        }
    }
}

DvmhPieces *piecesSubtract(int rank, DvmhPieces *pieces1, DvmhPieces *pieces2) {
    DvmhPieces *res = piecesDup(rank, pieces1);
    int i;
    for (i = 0; i < pieces2->piecesCount; i++) {
        piecesSubtractOne(rank, res, pieces2->pieces[i]);
    }
    return res;
}

DvmhPieces *piecesIntersect(int rank, DvmhPieces *p1, DvmhPieces *p2) {
    if (p1->piecesCount == 0 || p2->piecesCount == 0)
        return piecesNew(0, rank);
    if (rank == 0)
        return piecesNew(1, rank);
    DvmhPieces *tmp1 = piecesNew(1, rank);
    memcpy(tmp1->pieces[0], p1->pieces[0], sizeof(Interval) * rank);
    int i;
    for (i = 1; i < p1->piecesCount; i++) {
        int j;
        for (j = 0; j < rank; j++) {
            if (tmp1->pieces[0][j][0] > p1->pieces[i][j][0])
                tmp1->pieces[0][j][0] = p1->pieces[i][j][0];
            if (tmp1->pieces[0][j][1] < p1->pieces[i][j][1])
                tmp1->pieces[0][j][1] = p1->pieces[i][j][1];
        }
    }
    DvmhPieces *tmp2 = piecesSubtract(rank, tmp1, p2);
    DvmhPieces *res = piecesSubtract(rank, p1, tmp2);
    piecesDelete(tmp1);
    piecesDelete(tmp2);
    return res;
}

void piecesUniteOne(int rank, DvmhPieces *p, Interval *inter) {
    piecesSubtractOne(rank, p, inter);
    piecesAppendOne(rank, p, inter);
}

void piecesOutOne(int level, const char *filename, int lineNumber, int rank, Interval *inter) {
    char buf[300];
    char *cb = buf;
    *cb = 0;
    int j;
    for (j = 0; j < rank; j++) {
        int written = sprintf(cb, "[%ld..%ld]", inter[j][0], inter[j][1]);
        cb += written;
        *cb = 0;
    }
    dvmh_log_ex(level, filename, lineNumber, "%s", buf);
}

void piecesOut(int level, const char *filename, int lineNumber, int rank, DvmhPieces *p) {
    if (!p)
        return;
    int i;
    for (i = 0; i < p->piecesCount; i++) {
        piecesOutOne(level, filename, lineNumber, rank, p->pieces[i]);
    }
}

// Dictionary
Dictionary *dictNew() {
    Dictionary *dict = (Dictionary *)malloc(sizeof(Dictionary));
    dict->recordsCount = 0;
    dict->records = 0;
    return dict;
}

void *dictFind(Dictionary *dict, void *key) {
    int i;
    for (i = 0; i < dict->recordsCount; i++)
        if (dict->records[i].key == key)
            return dict->records[i].value;
    return 0;
}

int dictErase(Dictionary *dict, void *key) {
    int i;
    for (i = 0; i < dict->recordsCount; i++)
        if (dict->records[i].key == key) {
            if (i < dict->recordsCount - 1)
                dict->records[i] = dict->records[dict->recordsCount - 1];
            dict->recordsCount--;
            if (dict->recordsCount)
                dict->records = (typeof(dict->records))realloc(dict->records, sizeof(*dict->records) * dict->recordsCount);
            else
                free(dict->records);
            return 1;
        }
    return 0;
}

int dictAdd(Dictionary *dict, void *key, void *value) {
    if (dictFind(dict, key) == 0) {
        dict->recordsCount++;
        if (dict->records)
            dict->records = (typeof(dict->records))realloc(dict->records, sizeof(*dict->records) * dict->recordsCount);
        else
            dict->records = (typeof(dict->records))malloc(sizeof(*dict->records) * dict->recordsCount);
        dict->records[dict->recordsCount - 1].key = key;
        dict->records[dict->recordsCount - 1].value = value;
        return 1;
    } else
        return 0;
}

void dictSet(Dictionary *dict, void *key, void *value) {
    if (dictFind(dict, key) != 0)
        dictErase(dict, key);
    dictAdd(dict, key, value);
}

void dictForEach(Dictionary *dict, void (*f)(), int numArgs, ...) {
    if (numArgs < 0)
        numArgs = 0;
    void *params[numArgs + 2];
    if (numArgs > 0) {
        va_list ap;
        va_start(ap, numArgs);
        int i;
        for (i = 0; i < numArgs; i++)
            params[i] = va_arg(ap, void *);
        va_end(ap);
    }
    int i;
    for (i = 0; i < dict->recordsCount; i++) {
        params[numArgs] = dict->records[i].key;
        params[numArgs + 1] = dict->records[i].value;
        executeFunction(f, params, numArgs + 2);
    }
}

void dictDelete(Dictionary *dict) {
    if (dict->records)
        free(dict->records);
    free(dict);
}


void fillHeader(int rank, int typeSize, void *devAddr, Interval *portion, long *header) {
    long collector = 1;
    long offset = 0;
    int i;
    for (i = rank - 1; i >= 0; i--) {
        offset += collector * portion[i][0];
        header[i + 1] = collector;
        collector *= portion[i][1] - portion[i][0] + 1;
    }
    header[rank] = ((long)devAddr - header[rank + 2]) / typeSize;
    header[rank + 1] = ((long)devAddr - header[rank + 2]) / typeSize - offset;
}

#define UNDEF_BOUND (-2147483648)
void fillRealBlock(int rank, long *lowIndex, long *highIndex, Interval *havePortion, Interval *realBlock) {
    int i;
    for (i = 0; i < rank; i++) {
        if (lowIndex[i] == UNDEF_BOUND)
            realBlock[i][0] = havePortion[i][0];
        else
            realBlock[i][0] = lowIndex[i];
        if (highIndex[i] == UNDEF_BOUND)
            realBlock[i][1] = havePortion[i][1];
        else
            realBlock[i][1] = highIndex[i];
    }
}
#undef UNDEF_BOUND

int ilog(long value) {
    int res = -1;
    if (value)
        for (res = 0; (value & (1l << res)) == 0; res++) ;
    return res;
}

int gcd(int a, int b) {
    if (b == 0)
        return a;
    return gcd(b, a % b);
}

void executeFunction(void (*f)(), void **params, int paramsCount) {
    switch (paramsCount) {
        case 0:
            f();
            break;
        case 1:
            f(params[0]);
            break;
        case 2:
            f(params[0], params[1]);
            break;
        case 3:
            f(params[0], params[1], params[2]);
            break;
        case 4:
            f(params[0], params[1], params[2], params[3]);
            break;
        case 5:
            f(params[0], params[1], params[2], params[3], params[4]);
            break;
        case 6:
            f(params[0], params[1], params[2], params[3], params[4], params[5]);
            break;
        case 7:
            f(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
            break;
        case 8:
            f(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
            break;
        case 9:
            f(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8]);
            break;
        case 10:
            f(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9]);
            break;
    }
}
